package task

import (
	"context"
	"encoding/json"
	"fmt"
	"math"
	"pids-cloud-server/entity"
	"pids-cloud-server/logging"
	"strings"
	"sync"
	"time"

	"pids-cloud-server/utils"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/network"
	"github.com/docker/docker/client"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

func DockerTask() {
	logging.Info("---------定时采集docker容器数据任务开始---------")
	start := time.Now()

	// 获取数据库连接
	db := utils.GetDB()
	if db == nil {
		logging.Error("数据库连接失败，跳过Docker采集任务")
		return
	}

	var dockerInfos []entity.DockerInfo
	db.Model(&entity.DockerInfo{}).Find(&dockerInfos)
	var wg sync.WaitGroup                                   // 创建WaitGroup
	containerChannel := make(chan entity.ContainerInfo, 10) // 缓冲 channel 避免阻塞
	for i := range dockerInfos {
		wg.Add(1) // 每启动一个协程，增加一个计数
		dockerInfo := dockerInfos[i]
		go CollectRemoteDockerMetrics(dockerInfo, containerChannel, &wg)
	}
	// 等待所有 goroutine 完成后关闭 channel
	go func() {
		wg.Wait()
		close(containerChannel)
	}()

	// 收集所有容器信息到列表
	var allContainerInfos []entity.ContainerInfo
	for containerInfo := range containerChannel {
		allContainerInfos = append(allContainerInfos, containerInfo)
	}
	// 调用 UpsertContainerInfo
	if err := UpsertContainerInfo(db, allContainerInfos); err != nil {
		logging.Error("Failed to upsert container info:", err)
	} else {
		logging.Infof("Upsert docker 容器信息success!")
	}
	logging.Info("定时采集docker容器数据任务结束---------耗时：", time.Since(start))
}

// UpsertContainerInfo 使用 OnConflict 实现插入或更新
func UpsertContainerInfo(db *gorm.DB, containerInfoList []entity.ContainerInfo) error {
	// 插入或更新操作
	err := db.Clauses(clause.OnConflict{
		Columns:   []clause.Column{{Name: "docker_ip"}, {Name: "container_name"}},                                                                                                          // 唯一约束字段
		DoUpdates: clause.AssignmentColumns([]string{"image_name", "cpu_usage", "mem_usage", "state", "status", "ports", "networks", "mounts", "created_at", "started_at", "update_time"}), // 要更新的字段
	}).CreateInBatches(&containerInfoList, 100).Error
	return err
}
func CollectRemoteDockerMetrics(dockerInfo entity.DockerInfo, ch chan<- entity.ContainerInfo, wg *sync.WaitGroup) {
	defer wg.Done() // 协程结束时减少计数
	remoteUrl := fmt.Sprintf("tcp://%s:%s", dockerInfo.IP, dockerInfo.Port)
	// 创建连接远程 DockerInfo 主机的客户端
	cli, err := client.NewClientWithOpts(client.WithHost(remoteUrl), client.WithAPIVersionNegotiation())
	if err != nil {
		logging.Fatalf("Failed to create DockerInfo client: %v", err)
		return
	}
	defer cli.Close()

	ctx := context.Background()
	// 获取所有网络信息
	networks, err := cli.NetworkList(context.Background(), types.NetworkListOptions{})
	if err != nil {
		logging.Error("Failed to list Docker networks: ", err)
	}
	// 创建 map 存储网络信息，key 为网络名称
	networkMap := make(map[string]types.NetworkResource)
	// 遍历网络信息并存储到 map 中
	for _, network := range networks {
		networkMap[network.Name] = network
	}
	start := time.Now()
	// 获取容器列表
	containers, err := cli.ContainerList(ctx, types.ContainerListOptions{All: true})
	if err != nil {
		logging.Error("Failed to list containers: ", err)
		return
	}
	logging.Info("获取容器列表---------耗时：", time.Since(start))
	// 内层 goroutine 并发处理每个容器的统计信息
	var containerWg sync.WaitGroup // 创建内层 WaitGroup 来等待每个容器的统计信息
	// 遍历容器并打印信息
	for _, container := range containers {
		containerWg.Add(1) // 每启动一个容器的统计信息请求，增加一个计数

		// 启动 goroutine 处理每个容器的统计信息
		go func(container types.Container) {
			defer containerWg.Done() // 内层 goroutine 完成时减少计数
			start1 := time.Now()
			// 获取容器详细统计信息
			stats, err := cli.ContainerStats(context.Background(), container.ID, false)
			if err != nil {
				logging.Errorf("Failed to get stats for container %s: %v\n", container.ID, err)
				return
			}
			logging.Info("获取容器信息---------耗时：", time.Since(start1))
			// 使用 decoder 解码 JSON 数据
			var statsData types.StatsJSON
			if err := decodeStats(stats, &statsData); err != nil {
				logging.Errorf("Failed to decode stats for container %s: %v\n", container.ID, err)
				return
			}

			// 计算 CPU 使用率
			cpuUsage := calculateCPUUsage(&statsData)
			// 打印内存使用信息
			memUsage := statsData.MemoryStats.Usage
			memLimit := statsData.MemoryStats.Limit
			memUsagePer := math.Round(float64(memUsage) / float64(memLimit) * 100)

			// 获取端口映射信息
			portMappings := container.Ports
			portMappingStr := getPortMappings(portMappings)
			inspectData, err := cli.ContainerInspect(context.Background(), container.ID)
			if err != nil {
				logging.Errorf("Failed to inspect container %s: %v", container.ID, err)
				return
			}
			contarinerCreatecAt := time.Unix(container.Created, 0) //容器创建时间
			// 使用 time.Parse 方法将容器启动时间转换为 time.Time 类型
			contarinerStartedAt, err := time.Parse(time.RFC3339, inspectData.State.StartedAt) //容器启动时间
			if err != nil {
				logging.Errorf("Failed to parse container StartedAt %s: %v", container.ID, err)
			}
			networkmappingsStr := getNetworkMappings(inspectData.NetworkSettings.Networks, networkMap)
			mountsStr := getMounts(inspectData.Mounts)
			containerInfo := entity.ContainerInfo{
				DockerIp:      dockerInfo.IP,
				ContainerName: container.Names[0],
				ImageName:     container.Image,
				State:         container.State,
				CpuUsage:      cpuUsage,
				MemUsage:      memUsagePer,
				Ports:         portMappingStr,
				Networks:      networkmappingsStr,
				Mounts:        mountsStr,
				Status:        container.Status,
				CreatedAt:     contarinerCreatecAt,
				StartedAt:     contarinerStartedAt,
			}
			ch <- containerInfo // 将容器信息发送到 channel
		}(container)
	}
	containerWg.Wait() // 等待所有容器的统计信息请求完成
}

// getMounts 将 types.MountPoint切片 转换为字符串形式
func getMounts(mounts []types.MountPoint) string {
	var result []string
	// 获取容器的网络信息
	for networkName := range mounts {
		mapping := fmt.Sprintf("%s:%s", mounts[networkName].Source, mounts[networkName].Destination)
		result = append(result, mapping)
	}
	// 使用逗号拼接
	return strings.Join(result, ",")
}

// getNetworkMappings 将 map[string]*network.EndpointSettings 转换为字符串形式
func getNetworkMappings(networks map[string]*network.EndpointSettings, networkMap map[string]types.NetworkResource) string {
	var result []string
	// 获取容器的网络信息
	for networkName, _ := range networks {
		if network, exists := networkMap[networkName]; exists {
			mapping := fmt.Sprintf("%s:%s", network.Driver, network.Name)
			result = append(result, mapping)
		}
	}
	// 使用逗号拼接
	return strings.Join(result, ",")
}

// getPortMappings 将 types.Port切片 转换为字符串形式
func getPortMappings(ports []types.Port) string {
	var result []string
	for i := range ports {
		port := ports[i]
		// 拼接容器端口和主机端口
		mapping := fmt.Sprintf("%d:%d", port.PublicPort, port.PrivatePort)
		result = append(result, mapping)
	}
	// 使用逗号拼接
	return strings.Join(result, ",")
}

// decodeStats 解析容器统计数据
func decodeStats(stats types.ContainerStats, statsData *types.StatsJSON) error {
	decoder := json.NewDecoder(stats.Body)
	defer stats.Body.Close()
	return decoder.Decode(statsData)
}

// calculateCPUUsage 计算 CPU 使用率
func calculateCPUUsage(stats *types.StatsJSON) float64 {
	cpuDelta := float64(stats.CPUStats.CPUUsage.TotalUsage - stats.PreCPUStats.CPUUsage.TotalUsage)
	systemDelta := float64(stats.CPUStats.SystemUsage - stats.PreCPUStats.SystemUsage)

	if systemDelta > 0.0 && cpuDelta > 0.0 {
		cpuUsage := (cpuDelta / systemDelta) * float64(len(stats.CPUStats.CPUUsage.PercpuUsage)) * 100.0
		return math.Round(cpuUsage*100) / 100 // 保留 2 位小数
	}
	return 0.0
}
